package org.springframework.security.authentication.dao;

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.password.CompromisedPasswordChecker;
import org.springframework.security.authentication.password.CompromisedPasswordException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;

/**
 * @author Dillon
 * @date 2024/7/1
 * @slogan 致敬大师 致敬未来的你
 * @desc 认证实现类一种，基于数据源认证的方式
 */
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

	/**
	 * 默认查不到用于设置的密码
	 * 为了查不到用于也执行比较，防止恶意攻击
	 */
	private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";

	/**
	 * 密码加密器实现类
	 * 默认 BCryptPasswordEncoder
	 */
	private PasswordEncoder passwordEncoder;

	/**
	 * 默认找不到用户密码后 USER_NOT_FOUND_PASSWORD 加密后的字符串
	 */
	private volatile String userNotFoundEncodedPassword;

	/**
	 * 自定义的数据源实现类
	 */
	private UserDetailsService userDetailsService;

	/**
	 * 密码更新实现类
	 */
	private UserDetailsPasswordService userDetailsPasswordService;

	/**
	 * 密码泄露检查器实现类
	 */
	private CompromisedPasswordChecker compromisedPasswordChecker;

	/**
	 * 构造函数
	 */
	public DaoAuthenticationProvider() {
		this(PasswordEncoderFactories.createDelegatingPasswordEncoder());
	}

	/**
	 * 构造函数
	 * @param passwordEncoder 密码加密器
	 */
	public DaoAuthenticationProvider(PasswordEncoder passwordEncoder) {
		setPasswordEncoder(passwordEncoder);
	}

	/**
	 * 身份认证 凭证以及密码检查
	 * @param userDetails 查询到的userDetail 数据
	 * @param authentication 认证实现类
	 * @throws AuthenticationException 认证异常
	 */
	@Override
	@SuppressWarnings("deprecation")
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		// 凭证检查
		if (authentication.getCredentials() == null) {
			this.logger.debug("认证失败，凭证为空");
			throw new BadCredentialsException(this.messages
				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
		// 密码检查
		String presentedPassword = authentication.getCredentials().toString();
		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			this.logger.debug("认证失败 密码不匹配");
			throw new BadCredentialsException(this.messages
				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}
	}

	/**
	 * 后验检查
	 */
	@Override
	protected void doAfterPropertiesSet() {
		Assert.notNull(this.userDetailsService, "UserDetailsService 实现类为空");
	}

	/**
	 * 尝试从自定义 userDetailService 中获取 userDetails
	 * @param username 用户名
	 * @param authentication 认证实现类
	 * @return userDetails 实现类
	 * @throws AuthenticationException 认证异常
	 */
	@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
			// 获取UserDetails
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException("用户为空，违反接口约定，约定不能为空");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			// 模拟认证校验 防止定时攻击
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			// 异常包装
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

	/**
	 * 认证成功，创建认证对象 密码升级
	 * @param principal 认证凭证
	 * @param authentication 认证实现类
	 * @param user 查询到用户实现类
	 * @return 认证成功对象
	 */
	@Override
	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
		String presentedPassword = authentication.getCredentials().toString();
		// 判断密码是否泄露
		boolean isPasswordCompromised = this.compromisedPasswordChecker != null
				&& this.compromisedPasswordChecker.check(presentedPassword).isCompromised();
		// 密码泄露 抛出异常
		if (isPasswordCompromised) {
			throw new CompromisedPasswordException("密码可能泄露 请更改密码");
		}
		// 判断密码是否需要升级
		boolean upgradeEncoding = this.userDetailsPasswordService != null
				&& this.passwordEncoder.upgradeEncoding(user.getPassword());
		// 密码升级
		if (upgradeEncoding) {
			String newPassword = this.passwordEncoder.encode(presentedPassword);
			user = this.userDetailsPasswordService.updatePassword(user, newPassword);
		}
		return super.createSuccessAuthentication(principal, authentication, user);
	}

	/**
	 * 初始化默认获取不到密码的密码
	 * 防止认证攻击
	 */
	private void prepareTimingAttackProtection() {
		if (this.userNotFoundEncodedPassword == null) {
			this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
		}
	}

	/**
	 * 规避用户查不到 定时攻击
	 * 执行默认认证失败校验，模拟认证校验
	 * @param authentication 认证实现类
	 */
	private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
		if (authentication.getCredentials() != null) {
			String presentedPassword = authentication.getCredentials().toString();
			this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
		}
	}

	/**
	 * 设置密码加密器
	 * 同时将 userNotFoundEncodedPassword 重置 防止不重置 存在匹配上密码的概率
	 * @param passwordEncoder 密码加密器
	 */
	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		Assert.notNull(passwordEncoder, "密码加密器不能为空");
		this.passwordEncoder = passwordEncoder;
		this.userNotFoundEncodedPassword = null;
	}

	/**
	 * 获取密码加密器
	 * @return 密码加密器
	 */
	protected PasswordEncoder getPasswordEncoder() {
		return this.passwordEncoder;
	}

	/**
	 * 设置自定义的 数据源获取实现类
	 * @param userDetailsService 自定义数据源获取实现类
	 */
	public void setUserDetailsService(UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}

	/**
	 * 获取自定义的 数据源获取实现类
	 * @return 自定义数据源获取实现类
	 */
	protected UserDetailsService getUserDetailsService() {
		return this.userDetailsService;
	}

	/**
	 * 设置密码更新器
	 * @param userDetailsPasswordService 密码更新实现类
	 */
	public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
		this.userDetailsPasswordService = userDetailsPasswordService;
	}

	/**
	 * 设置密码泄露检查器
	 * @param compromisedPasswordChecker 密码泄露检查器
	 */
	public void setCompromisedPasswordChecker(CompromisedPasswordChecker compromisedPasswordChecker) {
		this.compromisedPasswordChecker = compromisedPasswordChecker;
	}

}
